﻿using Hinacom.Scheduler.Configuration;
using Hinacom.Scheduler.Manager.Utilities;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Windows.Forms;

namespace Hinacom.Scheduler.Manager.Views
{
    public partial class JobInfoView : UserControl
    {
        public JobInfoView()
        {
            InitializeComponent();
            
            try { drpJobType.DataSource = new ArrayList(SchedulerConfiguration.Current.JobTypes); }
            catch { }

            drpScheduleType.DataSource = ScheduleType.ScheduleTypes;
            
            dataMap = new List<ControlDataMap>()
            {
                new ControlDataMap(txtName, "Name"),
                new ControlDataMap(txtGroup, "Group"),
                new ControlDataMap(txtDescription, "Description")
            }.ToArray();
        }

        private ControlDataMap[] dataMap;

        [Browsable(false)]
        [ToolboxItem(false)]
        public JobInfo JobInfo
        {
            get
            {
                if (DesignMode) return null;
                if (currentScheduleView == null) return null;

                IScheduleView scheduleView = (IScheduleView)currentScheduleView;
                JobInfo _jobInfo = scheduleView.CreateJobInfoInstance();
                _jobInfo.Name = txtName.Text;
                _jobInfo.Group = txtGroup.Text;
                _jobInfo.Description = txtDescription.Text;
                if (rdoStartAt.Checked)
                    _jobInfo.StartTimeUtc = dtpStartAt.Value.ToUniversalTime();
                if (rdoEndAt.Checked)
                    _jobInfo.EndTimeUtc = dtpEndAt.Value.ToUniversalTime();
                _jobInfo.JobTypeName = ((JobTypeConfiguration)drpJobType.SelectedItem).Name;
                // schedule details
                scheduleView.FillJobInfo(_jobInfo);
                // configured parameters
                if (propertyGrid1.SelectedObject != null && propertyGrid1.SelectedObject is JobParameter)
                    _jobInfo.Parameter.SetData((JobParameter)propertyGrid1.SelectedObject);
                // customized parameters
                _jobInfo.Parameter.SetData(GetCustomizedParameters());
                return _jobInfo;
            }
            set
            {
                if (DesignMode || value == null) return;
                txtName.Text = value.Name;
                txtGroup.Text = value.Group;
                txtDescription.Text = value.Description;
                ControlDataBinder.ApplyControlData(value, dataMap);

                #region Job Type Related
                foreach(JobTypeConfiguration jtconfig in drpJobType.Items)
                {
                    if (jtconfig.Name == value.JobTypeName)
                    {
                        drpJobType.SelectedItem = jtconfig;
                        break;
                    }
                }
                // set configured parameter value and collect the used data item keys
                currentJobParameterPreservedKeys = new List<string>() { "this", "Count", "Keys", "Values" };
                if (propertyGrid1.SelectedObject != null)
                {
                    ((JobParameter)propertyGrid1.SelectedObject).SetData(value.Parameter);
                    PropertyInfo[] props = propertyGrid1.SelectedObject.GetType().GetProperties();
                    foreach(PropertyInfo prop in props)
                    {
                        var keyattr = Attribute.GetCustomAttribute(prop, typeof(JobParameterDataItemKeyAttribute));
                        if (keyattr != null)
                            currentJobParameterPreservedKeys.AddRange(((JobParameterDataItemKeyAttribute)keyattr).DataItemKeys);
                    }
                }
                #endregion

                SetCustomizedParameters(value.Parameter);

                Type jobInfoType = value.GetType();
                ScheduleType scheduleType = ScheduleType.ScheduleTypes.FirstOrDefault(t => t.JobInfoType.Equals(jobInfoType));
                if (scheduleType != null)
                {
                    drpScheduleType.SelectedItem = scheduleType;
                    if (currentScheduleView != null)
                    {
                        ((IScheduleView)currentScheduleView).ApplyJobInfo(value);
                    }
                }
            }
        }

        [Browsable(false)]
        [ToolboxItem(false)]
        public bool EditMode
        {
            get { return !txtName.Enabled; }
            set { txtName.Enabled = txtGroup.Enabled = !value; }
        }

        public bool ValidateInput()
        {
            bool valid = true;
            StringBuilder messages = new StringBuilder();
            if (String.IsNullOrWhiteSpace( txtName.Text))
            {
                valid = false;
                messages.AppendLine("Task name is not provided.");
            }
            if (String.IsNullOrWhiteSpace(txtGroup.Text))
            {
                valid = false;
                messages.AppendLine("Task group is not provided.");
            }
            if (drpJobType.SelectedItem == null)
            {
                valid = false;
                messages.AppendLine("The job type is not specified.");
            }
            if (drpScheduleType.SelectedItem == null)
            {
                valid = false;
                messages.AppendLine("The schedule is not specified.");
            }
            if (currentScheduleView != null)
                valid = valid && ((IScheduleView)currentScheduleView).ValidateInput(messages);
            if (!valid)
            {
                MessageBox.Show(this.ParentForm, 
                    "There are some problems with your inputs. Plesae verify.\r\n" + messages.ToString(), 
                    "Invalid Input", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            }
            return valid;
        }

        private Control currentScheduleView = null;
        private List<string> currentJobParameterPreservedKeys = null;

        #region Control Events
        // Schedule Type Dropdown Selection Changed
        private void drpScheduleType_SelectedIndexChanged(object sender, System.EventArgs e)
        {
            RefreshScheduleView();
        }
        // Job Type Dropdown Selection Changed
        private void drpJobType_SelectedIndexChanged(object sender, System.EventArgs e)
        {
            RefreshJobTypeParameters();
        }
        // The checking status fo the four radio buttons in StartTime/EndTime fields changed
        private void RefreshTimeRange(object sender, EventArgs e)
        {
            RefreshTimeRange();
        }
        #endregion

        private void RefreshScheduleView()
        {
            if (currentScheduleView != null)
            {
                pnlScheduleDetails.Controls.Remove(currentScheduleView);
                currentScheduleView = null;
            }

            if (drpScheduleType.SelectedItem == null) return;

            ScheduleType scheduleType = (ScheduleType)drpScheduleType.SelectedItem;
            currentScheduleView = scheduleType.CreateViewInstance();
            currentScheduleView.Dock = DockStyle.Fill;
            pnlScheduleDetails.Controls.Add(currentScheduleView);
        }
        private void RefreshJobTypeParameters()
        {
            var jobConfig = (JobTypeConfiguration)drpJobType.SelectedItem;
            string[] typeParts = jobConfig.Type.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Where(s => !String.IsNullOrWhiteSpace(s)).Select(s => s.Trim()).ToArray();
            Assembly ass = Assembly.Load(typeParts[1]);
            Type jobType = ass.GetType(typeParts[0]);
            Type parameterType = null;
            JobParameterAttribute attr = (JobParameterAttribute)Attribute.GetCustomAttribute(jobType, typeof(JobParameterAttribute));
            if (attr != null)
            {
                parameterType = attr.Type;
            }

            if (parameterType != null)
            {
                //pnlSpecifiedParameter.Visible = true;
                var parameter = parameterType.GetConstructor(new Type[0]).Invoke(new object[0]);
                propertyGrid1.SelectedObject = parameter;
            }
            else
            {
                //pnlSpecifiedParameter.Visible = false;
                propertyGrid1.SelectedObject = null;
            }
        }
        private void RefreshTimeRange()
        {
            dtpStartAt.Enabled = rdoStartAt.Checked;
            dtpEndAt.Enabled = rdoEndAt.Checked;
        }
        
        private IDictionary<string, object> GetCustomizedParameters()
        {
            Dictionary<string, object> data = new Dictionary<string, object>();
            foreach(DataGridViewRow row in dataGridView1.Rows)
            {
                string key = ((string)row.Cells[0].Value ?? "").Trim();
                string type = (string)row.Cells[1].Value;
                string valueText = (string)row.Cells[2].Value;
                if (String.IsNullOrWhiteSpace(key)) continue;

                CustomizedDataItem dataItem = CustomizedDataItem.Create(key, type, valueText);
                if (dataItem == null) continue;
                data.Add(key, dataItem.Value);
            }
            return data;
        }
        private void SetCustomizedParameters(IDictionary<string, object> data)
        {
            dataGridView1.Rows.Clear();
            foreach (string key in data.Keys)
            {
                if (currentJobParameterPreservedKeys.Contains(key)) continue; // preserved

                CustomizedDataItem dataItem = CustomizedDataItem.Create(key, data[key]);
                if (dataItem == null) continue;

                int rowIndex = dataGridView1.Rows.Count - 1;
                if (rowIndex < 0) rowIndex = 0;
                dataGridView1.Rows.Insert(rowIndex, 1);
                var row = dataGridView1.Rows[rowIndex];
                row.Cells[0].Value = dataItem.Key;
                row.Cells[1].Value = dataItem.Type;
                row.Cells[2].Value = dataItem.ValueText;
            }
        }

        #region Customized Data Items
        private abstract class CustomizedDataItem
        {
            public static CustomizedDataItem Create(string key, string type, string valueText)
            {
                CustomizedDataItem dataItem = CreateInstanceFromTypeName(type);
                if (dataItem == null) return null;
                dataItem.Key = key;
                dataItem.Value = valueText;
                return dataItem;
            }
            public static CustomizedDataItem Create(string key, object value)
            {
                CustomizedDataItem dataItem = CreateInstanceFromValue(value);
                if (dataItem == null) return null;
                dataItem.Key = key;
                dataItem.Value = value;
                return dataItem;
            }
            private static CustomizedDataItem CreateInstanceFromTypeName(string type)
            {
                CustomizedDataItem dataItem = null;
                switch (type)
                {
                    case "String":
                        dataItem = new CustomizedStringDataItem();
                        break;
                    case "Boolean":
                        dataItem = new CustomizedBooleanDataItem();
                        break;
                    case "Integer":
                        dataItem = new CustomizedIntegerDataItem();
                        break;
                    case "DateTime":
                        dataItem = new CustomizedDateTimeDataItem();
                        break;
                }
                return dataItem;
            }
            private static CustomizedDataItem CreateInstanceFromValue(object value)
            {
                CustomizedDataItem dataItem = null;
                if (value is string)
                    dataItem = new CustomizedStringDataItem();
                else if (value is bool)
                    dataItem = new CustomizedBooleanDataItem();
                else if (value is int)
                    dataItem = new CustomizedIntegerDataItem();
                else if (value is DateTime)
                    dataItem = new CustomizedDateTimeDataItem();
                return dataItem;
            }
            public string Key { get; set; }
            public abstract string Type { get; }
            public abstract object Value { get; set; }
            public abstract string ValueText { get; }
        }
        private class CustomizedStringDataItem : CustomizedDataItem
        {
            public override string Type
            {
                get { return "String"; }
            }
            private string _value;
            public override object Value
            {
                get { return this._value; }
                set
                {
                    if (value == null)
                        this._value = "";
                    else
                        this._value = value.ToString();
                }
            }
            public override string ValueText
            {
                get { return _value; }
            }
        }
        private class CustomizedBooleanDataItem : CustomizedDataItem
        {
            public override string Type
            {
                get { return "Boolean"; }
            }
            private bool _value;
            public override object Value
            {
                get { return this._value; }
                set
                {
                    if (value == null)
                        this._value = default(bool);
                    else if (value is bool)
                        this._value = (bool)value;
                    else
                    {
                        bool b = default(bool);
                        if (Boolean.TryParse(value.ToString(), out b))
                            this._value = b;
                        else
                            this._value = default(bool);
                    }
                }
            }
            public override string ValueText
            {
                get { return _value.ToString(); }
            }
        }
        private class CustomizedIntegerDataItem : CustomizedDataItem
        {
            public override string Type
            {
                get { return "Integer"; }
            }
            private int _value;
            public override object Value
            {
                get { return this._value; }
                set
                {
                    if (value == null)
                        this._value = default(int);
                    else if (value is int)
                        this._value = (int)value;
                    else
                    {
                        int b = default(int);
                        if (Int32.TryParse(value.ToString(), out b))
                            this._value = b;
                        else
                            this._value = default(int);
                    }
                }
            }
            public override string ValueText
            {
                get { return _value.ToString(); }
            }
        }
        private class CustomizedDateTimeDataItem : CustomizedDataItem
        {
            public override string Type
            {
                get { return "DateTime"; }
            }
            private DateTime _value;
            public override object Value
            {
                get { return this._value; }
                set
                {
                    DateTime defaultValue = DateTime.Now;
                    if (value == null)
                        this._value = defaultValue;
                    else if (value is DateTime)
                        this._value = (DateTime)value;
                    else
                    {
                        DateTime b = defaultValue;
                        DateTime.TryParse(value.ToString(), out b);
                        this._value = b;
                    }
                }
            }
            public override string ValueText
            {
                get { return _value.ToString("yyyy-MM-dd HH:mm:ss"); }
            }
        }
        #endregion

        private class ScheduleType
        {
            private static ScheduleType[] _scheduleTypes = null;
            public static ScheduleType[] ScheduleTypes
            {
                get
                {
                    if (_scheduleTypes == null)
                    {
                        _scheduleTypes = new ScheduleType[]
                        {
                            new ScheduleType()
                            {
                                DisplayText = "Simple",
                                Description = "Run once or simply repeating jobs.",
                                ViewType = typeof(SimpleScheduleView),
                                JobInfoType = typeof(SimpleJobInfo)
                            },
                            new ScheduleType()
                            {
                                DisplayText = "Daily",
                                Description = "Run at certains times each day.",
                                ViewType = typeof(DailyScheduleView),
                                JobInfoType = typeof(DailyJobInfo)
                            },
                            new ScheduleType()
                            {
                                DisplayText = "Calendar",
                                Description = "",
                                ViewType = typeof(CalendarScheduleView),
                                JobInfoType = typeof(CalendarJobInfo)
                            }
                        };
                    }
                    return _scheduleTypes;
                }
            }

            public string DisplayText { get; set; }
            public string Description { get; set; }
            public Type ViewType { get; set; }
            public Type JobInfoType { get; set; }
            public override string ToString()
            {
                return String.Format("{0}: {1}", this.DisplayText, this.Description);
            }
            public Control CreateViewInstance()
            {
                return (Control)this.ViewType.GetConstructor(new Type[0]).Invoke(new object[0]);
            }
        }
    }
}
